<?php

class ss_json_query{
	const ENCLOSURE_TYPE_DEFAULT=0;
	const ENCLOSURE_TYPE_FILTER=1;
	const ENCLOSURE_TYPE_MAP=2;
	const ENCLOSURE_TYPE_SORT=3;
	const ENCLOSURE_TYPE_LIMIT=4;
	const ENCLOSURE_TYPE_UNION=5;
	public static $OBJECT_DELIMETER='\{\}';
	public static $COMMANDS=array('contains');
	public static $LOGICAL_DELIMETER='&|%';
	public static $EXPRESSION_DELIMETER=array(array('<=','>=','\!='),'\\=\\+\\-\\*\\/~<>'); //\+-\/\*=~<>
	
	public static $ENCLOSURE_BASE_CLASS='ss_json_query_';
	
	protected $query;
	
	public function __construct($query,$scope=array('pri'),$classBase='ss_json_'){
		//self::$root=$this;
		//self::$ENCLOSURE_BASE_CLASS;
		$this->scope=(is_array($scope)?$scope:array($scope));
		//ss_error::log($this->scope,'Root scope');
		$_split=preg_split("/([\[\]])/",$query,null,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
		$_split=$this->structure($_split);
		$this->query=new ss_json_query_block($this->scope,$this);
		$this->query->branch($_split);
		//ss_error::log($this->query,'Query');
		//ss_error::log($this->getDql(),'DQL');
	}
	
	protected function branch_old(&$split){
		$_scope=$this->scope;
		while(($_part=array_shift($split))!=']' && !is_null($_part)){
			//print('Part: '.$_part."\n");
			if($_part=='[') {
				$_class=$this->getEnclosureClass($split[0]);
				$_obj=$this->command[]=new $_class($this,$_scope,$split);
				//$_scope=$_obj->getScope();
			} else {
				$_class=$this->getEnclosureClass($_part);
				//ss_error::info($_class,'Enclosure class');
				$_obj=$this->command[]=new $_class($this,$_scope,$split);
				$_scope=$_obj->getScope();
				//$this->command[]=$this->parser($_part);
				/*$result=self::split($_part,$this->LOGICAL_DELIMETER,' ');
				foreach($result as $key=>$val){
					//print($val."\n");
					if(strpos($this->LOGICAL_DELIMETER,$val)===false) {
						$_obj=$result[$key]=new ss_json_query_expression($this,$_scope,$val);
						$_scope=$_obj->getScope();
					}
				}
				$this->command[]=$result;
				*/
			}
		}
	}
	
	protected function structure(&$split){
		$_result=array();
		while(($_part=array_shift($split))!==']' && !is_null($_part)){
			if($_part==='[') {
				$_result[]=$this->structure($split);
			} else {
				$_result[]=$_part;
			}
		}
		//ss_error::log($_result);
		return $_result;
	}
	
	public function setScope($scope){
		$this->scope=$scope;
	}
	
	public function getScope(){
		return $this->scope;
	}
	
	public function getDql(){
		$result=array();
		$this->query->getDql($result);
		//ss_error::log($result,'Result');
		return $result;
	}
}

class ss_json_query_block{
	protected $parent;
	protected $query=array();
	protected $scope;
	
	public function __construct($scope,$parent){
		$this->scope=$scope;
		$this->parent=$parent;
	}
	
	public function branch($query){
		foreach($query as $key=>$val){
			if(is_array($val)){
				$_obj=$this->query[]=new ss_json_query_block($this->scope,$this);
			} else {
				$_class=$this->getExpressionClass($val);
				$_obj=$this->query[]=new $_class($this->scope,$this);
			}
			$_obj->branch($val);
		}
		return $this;
	}
	
	protected function getExpressionClass($enc){
		switch($enc[0]){
			case '?':
				//$enc=substr($enc,1);
				return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_filter';
				break;
			case '=':
				return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_map';
				break;
			case '/':
			case '\\':
				return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_sort';
				break;
			default:
				if(strpos($enc,':')!==false){
					return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_limit';
				} elseif(strpos($enc,',')){
					return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_union';
				}
		}
		return ss_json_query::$ENCLOSURE_BASE_CLASS.'expression_default';
	}
	
	public function getDql(&$dql){
		foreach($this->query as $_com) $_com->getDql($dql);
		//ss_error::info($dql,'DQL');
	}
}

abstract class ss_json_query_base{
	protected $scope;
	protected $query;
	protected $parent;
	
	public function __construct(array $scope,$parent){
		$this->scope=$scope;
		$this->parent=$parent;
	}
	
	abstract public function branch($query);
	
	static public function regSplit($data,$reg,$trim=null){
		$cmd=(is_array($data) ? $data : array($data));
		//ss_error::log($split);
		foreach($cmd as $key=>$var){
			$result=preg_split($reg,$var,null,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
			if(!is_null($trim)) self::array_trim($result,$trim);
			$cmd[$key]=$result;
		}
		return (is_array($data) ? $cmd : $cmd[0]);
	}
	
	static public function split($data,$split,$trim=null,$inclDel=true,$limit=-1){
		$cmd=(is_array($data) ? $data : array($data));
		$split=(is_array($split) ? (is_array($split[0])? '/((?>'.implode('|',$split[0]).")|(?>[{$split[1]}]))/" : '/('.implode('|',$split).')/' ) : "/([{$split}])/" );
		//ss_error::log($split);
		foreach($cmd as $key=>$var){
			$result=preg_split($split,$var,$limit,($inclDel?PREG_SPLIT_DELIM_CAPTURE:0)|PREG_SPLIT_NO_EMPTY);
			if(!is_null($trim)) self::array_trim($result,$trim);
			$cmd[$key]=$result;
		}
		return (is_array($data) ? $cmd : $cmd[0]);
	}
	
	static public function array_trim(&$array,$trim){
		array_walk($array, function(&$val,$pos,$trim){
			$val=trim($val,$trim);
		},$trim);
	}
}

class ss_json_query_expression extends ss_json_query_base{
	public function branch($query){
		$this->query=$query;
	}
}

class ss_json_query_expression_filter extends ss_json_query_expression{
	public function branch($query){
		$query=substr($query,1);
		$this->query=$this->split($query,ss_json_query::$LOGICAL_DELIMETER);
		foreach($this->query as $key=>$val){
			if(strpos(ss_json_query::$LOGICAL_DELIMETER,$val)===false) {
				$_obj=$this->query[$key]=new ss_json_query_instruction($this->scope,$this);
				$_obj->branch($val);
			}
		}
	}
	
	public function getDql(&$dql){
		$_op='andWhere';
		foreach($this->query as $_com){
			if(is_object($_com)){
				$_dql=$_com->getDql($dql);
				if(!empty($_dql)) $dql[$_op][]=$_dql;
			} else {
				switch($_com){
					case '|':
						$_op='orWhere';
						break;
					default:
						$_op='andWhere';
				}
			}
			//if(is_object($_com))print(get_class($_com));
			//$result[]=(is_object($_com)?$_com->getDql():$_logtrans[$_com]);
		}
	}
}

class ss_json_query_expression_sort extends ss_json_query_expression{
	public function branch($query){
		//$query=substr($query,1);
		$this->query=$this->split($query,',',' ',false);
		//ss_error::log($this->query,'Sort');
	}
	public function getDql(&$dql){
		$_result=array();
		foreach($this->query as $_com){
			$_dir=$_com[0]=='/'?'ASC':'DESC';
			$_com=trim($_com,'\/');
			$_result[]=$_com.' '.$_dir;
		}
		$dql['addOrderBy'][]=implode(', ',$_result);
	}
}

class ss_json_query_expression_limit extends ss_json_query_expression{
	public function branch($query){
		//$query=substr($query,1);
		$this->query=explode(':',$query);//$this->split($query,',',' ');
	}
	
	public function getDql(&$dql){
		$dql['offset'][]=$this->query[0];
		$dql['limit'][]=$this->query[1]-$this->query[0]+1;
	}
}

class ss_json_query_expression_default extends ss_json_query_expression{
	public function branch($query){
		//$query=substr($query,1);
		$this->query=$query;
	}
}
class ss_json_query_instruction extends ss_json_query_base{
	public function branch($query){
		$_skip=false;
		if(strpos($query,'(')!==false){
			$_query=$this->regSplit($query,"/([\(\)])/",' ');
			$_subject=explode('.',$_query[0]);
			$_class=ss_json_query::$ENCLOSURE_BASE_CLASS.'command_'.array_pop($_subject);
			$_args=explode(',',$_query[2]);
			$this->query=array(new $_class(array_merge($this->scope,$_subject),$this));
			$this->query[0]->branch($_args);
		} else {
			$_query=$this->split($query,ss_json_query::$EXPRESSION_DELIMETER,' ',true,2);
			//ss_error::log($_query,'Branch');
			foreach($_query as $key=>$val){
				if(!in_array($val,ss_json_query::$EXPRESSION_DELIMETER[0]) && (strpos(ss_json_query::$EXPRESSION_DELIMETER[1],$val)===false)) {
					/*if($_skip){
						$this->query[$key]=$val;
						$_skip=false;
					}else
					*/
					//if(strpos($val[0],"\"'")!==false){
					if($val[0]==='"' || $val[0]==="'"){
						$this->query[$key]=new ss_json_query_element_string($val);
					} elseif(is_numeric($val)) {
						$this->query[$key]=new ss_json_query_element_numeric($val);
					} else {
						$this->query[$key]=new ss_json_query_element_attribute($this->scope,$this,$val);
					}
				} else {
					$this->query[$key]=$val;
					//if($val==='~') $_skip=true;
				}
			}
			//$this->query=$_query;
		}
		//ss_error::log($this->query);
	}
    public function getDql(&$dql) {
		    $_result = '';
			$_strings=array();
			//ss_error::log($this->query,'Query');
            foreach ($this->query as $key=>$val) {
                if (is_object($val)) {
                    $_strings[$key]= $val->getDql($dql);
                } else {
                    $_strings[$key]= $val;
                }
            }
			//ss_error::log($_strings,'Strings');
			$_prop=array_shift($_strings);
			$_op=array_shift($_strings);
			$_cmp=implode('',$_strings);
			//ss_error::log($_strings,'Strings');
			if(($_op==='=' || $_op==='~') && $_cmp!=='*'){
				if(strpos($_cmp,'*')!==false){
					$_op=' LIKE ';
					$_cmp=strtr($_cmp,'*','%');
				}
				//$_result=array($_prop.$_op.' ?',trim($_cmp,"'\""));
				$_result=$_prop.$_op.$_cmp;
				//ss_error::log($_result);
			} else {
				$_result=$_prop.$_op.$_cmp;
			}
			/*
		    switch ($_op) {
		        case '~':
					$_prop=array_shift($_strings);
					unset($_strings[0]);
					//$_result=$_prop.' LIKE '.strtr(implode('',$_strings),'*','%');
					$_result=array($_prop.' LIKE ?',strtr(implode('',$_strings),'*','%'));
					//$_result=$_strings[0].' LIKE '.strtr($_strings[2],'*','%');
					//$_result=$_strings[0].' LIKE '.strtr($_strings[2],'*','%');
					//$_result=implode(' ',array_map(function($_val){switch($_val){case '~': return 'LIKE';case '*': return '%'; default: return $_val;}},$_strings));
		            ;
		            break;
		        default:
		            $_result = implode('',$_strings);
		    }
		    */
		    return $_result;
		}
	}




class ss_json_query_command extends ss_json_query_base{
	public function branch($query){
		ss_error::log('undefined','Command');
		ss_error::log($query,'Args');
		ss_error::log($this->scope,'Subject');
	}
}

class ss_json_query_command_contains extends ss_json_query_command{
	public function branch($query){
		$this->query=substr($query[0],strrpos($query[0],'/')+1);
		/*ss_error::log('contains','Command');
		ss_error::log($query,'Args');
		ss_error::log($this->scope,'Subject');*/
	}
	
	public function getDql(&$dql){
		// Add leftjoin here?
		return implode('.',$this->scope).'.id='.$this->query;
		return array('andWhere'=>array(implode('.',$this->scope.'id='.$this->query)));
	}
}

class ss_json_query_element{
	protected $element;
	
	public function __construct($element){
		$this->element=$element;
	}
	
	public function getDql(&$dql){
		return $this->element;
	}
}

class ss_json_query_element_string extends ss_json_query_element{
}

class ss_json_query_element_numeric extends ss_json_query_element{
}

class ss_json_query_element_attribute extends ss_json_query_element{
	protected $scope;
	protected $parent;
	
	public function __construct(array $scope,$parent, $element){
		$this->scope=$scope;
		$this->parent=$parent;
		parent::__construct($element);
	}
	
	public function getDql(&$dql){
		return implode('.',$this->scope).'.'.$this->element;
	}
}
class ss_json_query_old{
	const ENCLOSURE_TYPE_DEFAULT=0;
	const ENCLOSURE_TYPE_FILTER=1;
	const ENCLOSURE_TYPE_MAP=2;
	const ENCLOSURE_TYPE_SORT=3;
	const ENCLOSURE_TYPE_LIMIT=4;
	const ENCLOSURE_TYPE_UNION=5;
	public $LOGICAL_DELIMETER='&|%';
	public $EXPRESSION_DELIMETER=array(array('<=','>=','\!='),'\\=\\+\\-\\*\\/~<>'); //\+-\/\*=~<>
	
	static $ENCLOSURE_BASE_CLASS='ss_json_query_';
	
	static public $root;
	protected $command=array();
	protected $scope=array();
	
	public function __construct($query,$scope=array('pri'),$classBase='ss_json_'){
		self::$root=$this;
		//self::$ENCLOSURE_BASE_CLASS;
		$this->scope=(is_array($scope)?$scope:array($scope));
		//ss_error::log($this->scope,'Root scope');
		$_split=preg_split("/([\[\]])/",$query,null,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
		$this->branch($_split);
	}
	
	protected function branch(&$split){
		$_scope=$this->scope;
		while(($_part=array_shift($split))!=']' && !is_null($_part)){
			//print('Part: '.$_part."\n");
			if($_part=='[') {
				$_class=$this->getEnclosureClass($split[0]);
				$_obj=$this->command[]=new $_class($this,$_scope,$split);
				//$_scope=$_obj->getScope();
			} else {
				$_class=$this->getEnclosureClass($_part);
				ss_error::info($_class,'Enclosure class');
				$_obj=$this->command[]=new $_class($this,$_scope,$split);
				$_scope=$_obj->getScope();
				//$this->command[]=$this->parser($_part);
				/*$result=self::split($_part,$this->LOGICAL_DELIMETER,' ');
				foreach($result as $key=>$val){
					//print($val."\n");
					if(strpos($this->LOGICAL_DELIMETER,$val)===false) {
						$_obj=$result[$key]=new ss_json_query_expression($this,$_scope,$val);
						$_scope=$_obj->getScope();
					}
				}
				$this->command[]=$result;
				*/
			}
		}
	}
	
	protected function getEnclosureClass(&$enc){
		switch($enc[0]){
			case '?':
				$enc=substr($enc,1);
				return self::$ENCLOSURE_BASE_CLASS.'filter';
				break;
			case '=':
				return self::$ENCLOSURE_BASE_CLASS.'map';
				break;
			case '/':
			case '\\':
				return self::$ENCLOSURE_BASE_CLASS.'sort';
				break;
			default:
				if(strpos($enc,':')){
					return self::$ENCLOSURE_BASE_CLASS.'limit';
				} elseif(strpos($enc,',')){
					return self::$ENCLOSURE_BASE_CLASS.'union';
				}
		}
		return self::$ENCLOSURE_BASE_CLASS.'default';
	}
	
	public function setScope($scope){
		$this->scope=$scope;
	}
	
	public function getScope(){
		return $this->scope;
	}
	
	public function getDql(&$result=array()){
		//$result=array();
		foreach($this->command as $_com){
			(is_object($_com)?$_com->getDql($result):$_com);
		}
		//ss_error::log($this->command,'Command');
		ss_error::log($result,'Result');
		return $result;
	}
	
	static public function split($data,$split,$trim=null){
		$cmd=(is_array($data) ? $data : array($data));
		$split=(is_array($split) ? (is_array($split[0])? '/((?>'.implode('|',$split[0]).")|(?>[{$split[1]}]))/" : '/('.implode('|',$split).')/' ) : "/([{$split}])/" );
		//ss_error::log($split);
		foreach($cmd as $key=>$var){
			$result=preg_split($split,$var,null,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
			if(!is_null($trim)) self::array_trim($result,$trim);
			$cmd[$key]=$result;
		}
		return (is_array($data) ? $cmd : $cmd[0]);
	}
	
	static public function array_trim(&$array,$trim){
		array_walk($array, function(&$val,$pos,$trim){
			$val=trim($val,$trim);
		},$trim);
	}
}

class ss_json_query_enclosure extends ss_json_query{
	protected $parent;
	
	public function __construct(ss_json_query $parent,$scope,&$split){
		$this->parent=$parent;
		$this->scope=$scope;
		//ss_error::log($this->scope,'Enc scope');
		$this->branch($split);
	}
}

class ss_json_query_filter extends ss_json_query_enclosure{
}

class ss_json_query_default extends ss_json_query_enclosure{
	
}

class ss_json_query_logical extends ss_json_query{
	protected $parent;
	protected $command;
	protected $scope=array();
	
	public function __construct(ss_json_query $parent,$scope,$split){
		$this->parent=$parent;
		$this->scope=$scope;
		$this->branch($split);
	}
	
	protected function branch(&$cmd){
		$_scope=$this->scope;
		$this->command=ss_json_query::split($cmd,$this->LOGICAL_DELIMETER,' ');
		foreach($this->command as $key=>$val){
			if(strpos($this->LOGICAL_DELIMETER,$val)===false) {
				$_obj=$this->command[$key]=new ss_json_query_expression($this,$_scope,$val);
				$_scope=$_obj->getScope();
			} else {
				$_scope=$this->scope;
			}
		}
		ss_error::log($this->command,'Logical');
	}
	
	public function getParent(){
		return $parent;
	}
	
	public function getScope(){
		return $this->scope;
	}
	
	public function getDql(&$result=array()){
		//$result=array();
		//$_logtrans=array('&'=>'AND','|'=>'OR');
		$_op='andWhere';
		foreach($this->command as $_com){
			if(is_object($_com)){
				$result[$_op][]=$_com->getDql($result);
			} else {
				switch($_com){
					case '|':
						$_op='notWhere';
						break;
					default:
						$_op='andWhere';
				}
			}
			//if(is_object($_com))print(get_class($_com));
			//$result[]=(is_object($_com)?$_com->getDql():$_logtrans[$_com]);
		}
		ss_error::log($result,'Logical result');
		//return implode(' ',$result);
	}
}

class ss_json_query_expression_old extends ss_json_query_logical{
	protected function branch(&$cmd){
		$_scope=$this->scope;
		$this->command=ss_json_query::split($cmd,$this->EXPRESSION_DELIMETER,' ');
		//print_r($this->command);
		foreach($this->command as $key=>$val){
			if(!in_array($val,$this->EXPRESSION_DELIMETER[0]) && (strpos($this->EXPRESSION_DELIMETER[1],$val)===false)) {
				if(strpos("\"'",$val[0])){
					$_obj=$this->command[$key]=new ss_json_query_string($this,$val);
				} elseif(is_numeric($val)) {
					$_obj=$this->command[$key]=new ss_json_query_numeric($this,$val);
				} else {
					//ss_error::log($_scope,'Sent scope');
					$_obj=$this->command[$key]=new ss_json_query_attribute($this,$_scope,$val);
					//$_scope=$_obj->getScope();
					$_scope=$_obj->getScope();
				}
			} else {
				$_scope=$this->scope;
			}
		}
	}
	
	public function getDql(&$result=array()){
		$return=array();
		foreach($this->command as $_com){
			//if(is_object($_com))print(get_class($_com));
			$return[]=(is_object($_com)?$_com->getDql($result):$_com);
		}
		return implode(' ',$return);
	}
}

class ss_json_query_attribute{
	protected $parent;
	protected $command;
	protected $scope=array();
	
	public function __construct($parent,$scope,$part){
		$this->parent=$parent;
		$this->scope=$scope;
		ss_error::log($this->scope,'Pre scope');
		$this->command=$this->branch($part);
		ss_error::log($this->scope,'Post scope');
	}
	
	protected function branch($com){
		$result=array();
		if($com[0]=='$'){
			$this->scope=ss_json_query::$root->getScope();
			//ss_error::log($this->scope,'Root scope');
			$com=substr($com,1);
		}if($com[0]=='@'){
			$com=substr($com,1);
		}
		if(!is_null(strpos($com,'('))){
			
		}
		$_prop=explode('.',trim($com,'.'));
		//ss_error::log($this->scope,'New scope');
		//$this->scope=array_merge($this->scope,array_slice($_prop,0,count($_prop)-1));
		$this->scope=array_merge($this->scope,$_prop);
		$result=array_merge($result,$_prop);
		return $result;
	}
	
	public function getScope(){
		return $this->scope;
	}
	
	public function getDql(&$result){
		ss_error::log($this->scope,'DQL scope');
		$result['leftJoin'][]=implode('.',array_slice($this->scope,0,count($this->scope)-1));
		return implode('.',$this->scope);
	}
}

class ss_json_query_var{
	protected $var;
	
	public function __construct($parent,$var){
		$this->var=$var;
	}
	
	public function getDql(&$result){
		return $this->var;
	}
}

class ss_json_query_string extends ss_json_query_var{
	
}

class ss_json_query_numeric extends ss_json_query_var{
	
}
?>